﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Citi.Import.Util;
using Citi.ResourceConsolidation.Domain.Utils;

namespace Citi.ResourceConsolidation.Domain
{
    public abstract class LevelParentImport<T> where T : Utils.GeneralAction
    {
        public static LevelParentImport<T> LevelParent;
        protected abstract string DescriptionLevelColumn { get; }
        private static string LastCode { get; set; }

        protected abstract string CodeLevel2Column { get; }
        protected abstract string CodeLevel3Column { get; }
        protected abstract string CodeLevel4Column { get; }
        protected abstract string CodeLevel5Column { get; }
        protected abstract string CodeLevel6Column { get; }
        protected abstract string CodeLevel7Column { get; }
        protected abstract string CodeLevel8Column { get; }
        protected abstract string CodeLevel9Column { get; }

        public static List<Definition<List<T>>> Definitions;

        protected LevelParentImport()
        {
            Definitions = GetDefinitions();
        }

        private List<Definition<List<T>>> GetDefinitions()
        {
            return new List<Definition<List<T>>>
            {
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel2Column, CustomConversionMethod = ValidateLevel2},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel3Column, CustomConversionMethod = ValidateLevel3},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel4Column, CustomConversionMethod = ValidateLevel4},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel5Column, CustomConversionMethod = ValidateLevel5},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel6Column, CustomConversionMethod = ValidateLevel6},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel7Column, CustomConversionMethod = ValidateLevel7},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel8Column, CustomConversionMethod = ValidateLevel8},
                new Definition<List<T>> { ModelProperty = x => x.Count, Required = true, ColumnName = CodeLevel9Column, CustomConversionMethod = ValidateLevel9},
            };
        }

        #region Custom Methods

        private static object ValidateLevel2(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 2);
        }

        private static object ValidateLevel3(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 3);
        }

        private static object ValidateLevel4(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 4);
        }

        private static object ValidateLevel5(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 5);
        }

        private static object ValidateLevel6(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 6);
        }

        private static object ValidateLevel7(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 7);
        }

        private static object ValidateLevel8(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 8);
        }

        private static object ValidateLevel9(ref List<T> model, string value, List<string> fields, ref string error, List<string> headers = null)
        {
            return ValidateLevel(model, value, fields, ref error, headers, 9);
        }

        private static object ValidateLevel(List<T> model, string value, List<string> fields, ref string error, List<string> headers, short level)
        {
            if (level == 2) LastCode = null;

            if (string.IsNullOrEmpty(LastCode) && level > 2)
                return Conversion.Validation.Validated;

            var descriptionColumn = string.Format(LevelParent.DescriptionLevelColumn, level);
            var description = fields[headers.IndexOf(Conversion.TransformColumnName(descriptionColumn))];

            if (string.IsNullOrWhiteSpace(description) || string.IsNullOrWhiteSpace(value))
            {
                LastCode = null;
                return Conversion.Validation.Validated;
            }

            var domainLevel = Activator.CreateInstance<T>();
            var domain = CacheFacade.Get(domainLevel.SelectAll<T>).FirstOrDefault(x => GetProperty("Id").GetValue(x, null).ToString() == value);

            if (domain != null)
            {
                GetProperty("Description").SetValue(domain, description, null);
                model.Add(domain);
            }
            else
            {
                GetProperty("Id").SetValue(domainLevel, value, null);
                GetProperty("Description").SetValue(domainLevel, description, null);
                GetProperty("ParentId").SetValue(domainLevel, string.IsNullOrWhiteSpace(LastCode) ? null : LastCode, null);
                GetProperty("Level").SetValue(domainLevel, level, null);

                model.Add(domainLevel);
            }

            LastCode = value;
            return Conversion.Validation.Validated;
        }

        private static PropertyInfo GetProperty(string propertyName)
        {
            return typeof(T).GetProperty(propertyName);
        }

        #endregion

        public static void Save<TComparer>(List<List<T>> items) where TComparer : IEqualityComparer<T>
        {
            var itemsList = new List<T>();
            items.ForEach(itemsList.AddRange);
            itemsList = itemsList.Distinct(Activator.CreateInstance<TComparer>()).ToList();

            if (!itemsList.Any()) return;

            var firstLevel = itemsList.Min(x => (short)GetProperty("Level").GetValue(x, null));
            var lastLevel = itemsList.Max(x => (short)GetProperty("Level").GetValue(x, null));

            for (int i = firstLevel; i <= lastLevel; i++)
            {
                var levels = itemsList.Where(x => (short)GetProperty("Level").GetValue(x, null) == i).ToList();

                if (!levels.Any()) continue;

                levels.First().Save(levels, 1);
            }

            CacheFacade.Clear(typeof(T));
            Context.ClearContext();
        }
    }
}
